#ifndef _RETRO_VECTOR2D
#define _RETRO_VECTOR2D

#define sqrtr               sqrtf
#define cosr                cosf
#define sinr                sinf
#define tanr                tanf
#define REAL_THRESHOLD      0.0001f
#define PI                  3.141592654f
#define RADIAN(degree)      ((degree) * PI / 180.0f)
#define DEGREE(radian)      ((radian) * 180.0f / PI)
#define RAND(from, to)      (rand() % ((to) - (from) + 1) + (from))

namespace Retro 
{
  namespace Vector 
  {
    struct Rect2D;
    struct Vect2D;
    struct Vector2D;

    // Vector Operators
    INL Vector2D operator+(const Vect2D &vect, REAL value);
    INL Vector2D operator-(const Vect2D &vect, REAL value);
    INL Vector2D operator*(const Vect2D &vect, REAL value);
    INL Vector2D operator*(REAL value, const Vect2D &vect);
    INL Vector2D operator/(const Vect2D &vect, REAL value);
    INL Vector2D operator*(const Vect2D &vect1, const Vect2D &vect2);
    INL Vector2D operator/(const Vect2D &vect1, const Vect2D &vect2);
    INL Vector2D operator-(const Vect2D &vect1, const Vect2D &vect2);
    INL Vector2D operator+(const Vect2D &vect1, const Vect2D &vect2);

    struct DLLEXPORT Vect2D 
    {
      REAL x;
      REAL y;

#pragma region Utilities
      //! Sets x and y to zero
      INL void zero();

      //! Checks if x and y are below the minimum real value
      INL BOOL isZero() const;

      //! Checks if the vector is inside the rectangle specified by two vectors
      INL BOOL isInside(
        const Vect2D &topLeft, 
        const Vect2D &bottomRight
        ) const;

      //! Checks if the vector is inside the specified rectangle
      INL BOOL isInside(const Rect2D &rect) const;

      //! Clips the vector coordinates to the rectangle specified by two vectors
      INL Vect2D& clip(
        const Vect2D &topLeft, 
        const Vect2D &bottomRight
        );

      //! Clips the vector coordinates to the specified rectangle
      INL Vect2D& clip(const Rect2D &rect);

      //! Wraps the vector coordinates to the rectangle specified by two vectors
      INL Vect2D& wrap(
        const Vect2D &topLeft, 
        const Vect2D &bottomRight
        );

      //! Wraps the vector coordinates to the specified rectangle
      INL Vect2D& wrap(const Rect2D &rect);
#pragma endregion

#pragma region Math
      //! Vector length (sqrt(x * x + y * y))
      INL REAL length() const;

      //! Squared vector length (x * x + y * y)
      INL REAL lengthSq() const;

      //! Normalize vector (x and y scaled to 0...1)
      INL Vect2D& normalize();

      //! Vector dot product  (x1 * x2 + y1 * y2)
      INL REAL dot(const Vect2D &vect) const;

      //! Vector sign (-1 if anticlockwise, 1 if clockwise)
      INL int sign(const Vect2D &vect) const;

      //! Returns a perpendicular vector
      INL Vect2D perp() const;

      //! Truncates the vector length to not exceed the specified value
      INL Vect2D& truncate(REAL max);

      //! Distance from the specified vector
      INL REAL distance(const Vect2D &vect) const;

      //! Squared distance from the specified vector
      INL REAL distanceSq(const Vect2D &vect) const;

      //! Returns the scale ratio with the given vector
      INL REAL ratio(const Vect2D &vect) const;

      //! Returns the inverted vector of this one (-x, -y)
      INL Vect2D reverse() const;

      //! Reflects this vector over the specified normalized vector
      INL Vect2D& reflect(const Vect2D &norm);

      //! Projects this vector onto the given vector
      INL Vect2D project(const Vect2D &vect) const;

      //! Rotates this vector over the given point
      INL Vect2D rotate(const Vect2D &origin, REAL angle) const;
#pragma endregion

#pragma region Operators
      //! Returns a GDI size
      INL operator SIZE() const
      {
        SIZE size = { (LONG)x, (LONG)y };
        return size;
      }

      //! Returns a GDI point
      INL operator POINT() const
      {
        POINT point = { (LONG)x, (LONG)y };
        return point;
      }

      INL Vect2D& operator+=(const Vect2D &vect2D);

      INL Vect2D& operator-=(const Vect2D &vect2D);

      INL Vect2D& operator*=(const Vect2D &vect2D);

      INL Vect2D& operator/=(const Vect2D &vect2D);

      INL Vect2D& operator+=(const REAL& value);

      INL Vect2D& operator-=(const REAL& value);

      INL Vect2D& operator*=(const REAL& value);

      INL Vect2D& operator/=(const REAL& value);

      INL BOOL operator==(const Vect2D& vect) const;

      INL BOOL operator!=(const Vect2D& vect) const;
#pragma endregion
    };

    struct DLLEXPORT Rect2D 
    {
      union 
      {
        //! Rectangle dimentions
        Vect2D size;

        struct
        {
          //! Rectangle width
          REAL width;
          //! Rectangle height
          REAL height;
        };

        struct 
        {
          //! Rectangle horizontal size
          REAL cx;
          //! Rectangle vertical size
          REAL cy;
        };
      };

      union 
      {
        //! Rectangle coordinates
        Vect2D point;

        struct
        {
          //! Rectangle left bound
          REAL left;
          //! Rectangle top bound
          REAL top;
        };

        struct 
        {
          //! Rectangle horizontal coordinate
          REAL x;
          //! Rectangle vertical coordinate
          REAL y;
        };
      };
    };

    struct DLLEXPORT Vector2D : Vect2D
    {
#pragma region Constructors
      INL Vector2D()
      {
      }

      INL Vector2D(REAL x, REAL y)
      {
        this->x = x;
        this->y = y;
      }

      INL Vector2D(const Vect2D &vect2D)
      {
        x = vect2D.x;
        y = vect2D.y;
      }
#pragma endregion
    };

    //! Sets x and y to zero
    void Vect2D::zero()
    {
      x = y = 0.0f;
    }

    //! Checks if x and y are below the minimum real value
    BOOL Vect2D::isZero() const
    {
      return x < REAL_THRESHOLD && y < REAL_THRESHOLD;
    }

    //! Checks if the vector is inside the rectangle specified by two vectors
    BOOL Vect2D::isInside(
      const Vect2D &topLeft, 
      const Vect2D &bottomRight
      ) const
    {
      return (x >= topLeft.x) && (x <= bottomRight.x) &&
        (y >= topLeft.y) && (y <= bottomRight.y);
    }

    //! Checks if the vector is inside the specified rectangle
    BOOL Vect2D::isInside(const Rect2D &rect) const
    {
      return (x >= rect.x) && (x <= rect.x + rect.cx) &&
        (y >= rect.y) && (y <= rect.y + rect.cy);
    }

    //! Clips the vector coordinates to the rectangle specified by two vectors
    Vect2D& Vect2D::clip(
      const Vect2D &topLeft,
      const Vect2D &bottomRight
      )
    {
      if (x < topLeft.x) x = topLeft.x;
      if (y < topLeft.y) y = topLeft.y;
      if (x > bottomRight.x) x = bottomRight.x;
      if (y > bottomRight.y) y = bottomRight.y;

      return *this;
    }

    //! Clips the vector coordinates to the specified rectangle
    Vect2D& Vect2D::clip(const Rect2D &rect)
    {
      if (x < rect.x) x = rect.x;
      if (y < rect.y) y = rect.y;
      if (x > rect.x + rect.cx) x = rect.x + rect.cx;
      if (y > rect.y + rect.cy) y = rect.y + rect.cy;

      return *this;
    }

    //! Wraps the vector coordinates to the rectangle specified by two vectors
    Vect2D& Vect2D::wrap(
      const Vect2D &topLeft, 
      const Vect2D &bottomRight
      )
    {
      if (x < topLeft.x) x += bottomRight.x + topLeft.x;
      if (y < topLeft.y) y += bottomRight.y + topLeft.y;
      if (x > bottomRight.x) x -= bottomRight.x + topLeft.x;
      if (y > bottomRight.y) y -= bottomRight.y + topLeft.y;

      return *this;
    }

    //! Wraps the vector coordinates to the specified rectangle
    Vect2D& Vect2D::wrap(const Rect2D &rect)
    {
      if (x < rect.x) x += rect.cx;
      if (y < rect.y) y += rect.cy;
      if (x > rect.x + rect.cx) x -= rect.cx;
      if (y > rect.y + rect.cy) y -= rect.cy;

      return *this;
    }

    Vect2D Vect2D::rotate(const Vect2D &origin, REAL angle) const
    {
      return Vector2D(
        cosf(angle) * (x - origin.x) -
        sinf(angle) * (y - origin.y) + origin.x,
        sinf(angle) * (x - origin.x) +
        cosf(angle) * (y - origin.y) + origin.y);
    }

    //! Vector length (sqrt(x * x + y * y))
    REAL Vect2D::length() const
    {
      return sqrtr(x * x + y * y);
    }

    //! Squared vector length (x * x + y * y)
    REAL Vect2D::lengthSq() const
    {
      return x * x + y * y;
    }

    //! Normalize vector (x and y scaled to 0...1)
    Vect2D& Vect2D::normalize()
    {
      REAL vectLength = length();

      x /= vectLength;
      y /= vectLength;

      return *this;
    }

    //! Vector dot product  (x1 * x2 + y1 * y2)
    REAL Vect2D::dot(const Vect2D &vect) const
    {
      return x * vect.x + y * vect.y;
    }

    //! Vector sign (-1 if anticlockwise, 1 if clockwise)
    int Vect2D::sign(const Vect2D &vect) const
    {
      return (y * vect.x > x * vect.y) ? -1 : 1;
    }

    //! Returns a perpendicular vector
    Vect2D Vect2D::perp() const
    {
      return Vector2D(-y, x);
    }

    //! Truncates the vector length to not exceed the specified value
    Vect2D& Vect2D::truncate(REAL max)
    {
      if (lengthSq() > max * max) {
        normalize();
        *this *= max;
      }

      return *this;
    }

    //! Distance from the specified vector
    REAL Vect2D::distance(const Vect2D &vect) const
    {
      Vect2D dist = vect - *this;
      return sqrtr(dist.x * dist.x + dist.y * dist.y);
    }

    //! Squared distance from the specified vector
    REAL Vect2D::distanceSq(const Vect2D &vect) const
    {
      Vect2D dist = vect - *this;
      return dist.x * dist.x + dist.y * dist.y;
    }

    //! Returns the scale ratio with the given vector
    REAL Vect2D::ratio(const Vect2D &vect) const
    {
      return (vect.y - y) / (vect.x - x);
    }

    //! Returns the inverted vector of this one (-x, -y)
    Vect2D Vect2D::reverse() const
    {
      return Vector2D(-x, -y);
    }

    //! Reflects this vector over the specified normalized vector
    Vect2D& Vect2D::reflect(const Vect2D &norm)
    {
      *this += 2.0f * dot(norm) * norm.reverse();
      return *this;
    }

    //! Projects this vector onto the given vector
    Vect2D Vect2D::project(const Vect2D &vect) const
    {
      // NOT TESTED, MIGHT BE WRONG
      REAL vectLength = vect.length();
      return (*this * vect) / (vectLength * vectLength) * vect;
    }

    Vect2D& Vect2D::operator+=(const Vect2D &vect2D)
    {
      x += vect2D.x;
      y += vect2D.y;
      return *this;
    }

    Vect2D& Vect2D::operator-=(const Vect2D &vect2D)
    {
      x -= vect2D.x;
      y -= vect2D.y;
      return *this;
    }

    Vect2D& Vect2D::operator*=(const Vect2D &vect2D)
    {
      x *= vect2D.x;
      y *= vect2D.y;
      return *this;
    }

    Vect2D& Vect2D::operator/=(const Vect2D &vect2D)
    {
      x /= vect2D.x;
      y /= vect2D.y;
      return *this;
    }

    Vect2D& Vect2D::operator+=(const REAL& value)
    {
      x += value;
      y += value;
      return *this;
    }

    Vect2D& Vect2D::operator-=(const REAL& value)
    {
      x -= value;
      y -= value;
      return *this;
    }

    Vect2D& Vect2D::operator*=(const REAL& value)
    {
      x *= value;
      y *= value;
      return *this;
    }

    Vect2D& Vect2D::operator/=(const REAL& value)
    {
      x /= value;
      y /= value;
      return *this;
    }

    BOOL Vect2D::operator==(const Vect2D& vect) const
    {
      return x == vect.x && y == vect.y;
    }

    BOOL Vect2D::operator!=(const Vect2D& vect) const
    {
      return x != vect.x || y != vect.y;
    }

#pragma region Operators
    INL Vector2D Retro::Vector::operator+(
      const Vect2D &vect, 
      REAL value
      )
    {
      return Vector2D(vect.x + value, vect.y + value);
    }

    INL Vector2D Retro::Vector::operator-(
      const Vect2D &vect, 
      REAL value
      )
    {
      return Vector2D(vect.x - value, vect.y - value);
    }

    INL Vector2D Retro::Vector::operator*(
      const Vect2D &vect, 
      REAL value
      )
    {
      return Vector2D(vect.x * value, vect.y * value);
    }

    INL Vector2D Retro::Vector::operator*(
      REAL value, 
      const Vect2D &vect
      )
    {
      return Vector2D(vect.x * value, vect.y * value);
    }

    INL Vector2D Retro::Vector::operator/(
      const Vect2D &vect, 
      REAL value
      )
    {
      return Vector2D(vect.x / value, vect.y / value);
    }

    INL Vector2D Retro::Vector::operator*(
      const Vect2D &vect1, 
      const Vect2D &vect2
      )
    {
      return Vector2D(vect1.x * vect2.x, vect1.y * vect2.y);
    }

    INL Vector2D Retro::Vector::operator/(
      const Vect2D &vect1, 
      const Vect2D &vect2
      )
    {
      return Vector2D(vect1.x / vect2.x, vect1.y / vect2.y);
    }

    INL Vector2D Retro::Vector::operator-(
      const Vect2D &vect1, 
      const Vect2D &vect2
      )
    {
      return Vector2D(vect1.x - vect2.x, vect1.y - vect2.y);
    }

    INL Vector2D Retro::Vector::operator+(
      const Vect2D &vect1, 
      const Vect2D &vect2
      )
    {
      return Vector2D(vect1.x + vect2.x, vect1.y + vect2.y);
    }
#pragma endregion
  } // namespace Vector
} // namespace Retro

#endif